<?php
/**
 * Holds class DBTable
 *
 * @author daantimmer
 *
 * @package Library
 */

/**
 * DBObject
 *
 * @author daantimmer
 */
class DBTable{

	protected $keys;

	protected $name;

	protected $db;

	protected $columns;
	
	protected static $cache = array();
	
	protected $lastError;

	public function __construct(Database $db,$name){
		$this->db = $db;
		$this->name = $name;
		
		if(isset(self::$cache[$name]) && true){
			$res = self::$cache[$name];
		}else{
			$query	=	"DESCRIBE {$name};";
			$stmt = $this->db->prepare($query);
			$res = self::$cache[$name] = $stmt->fetchObjectAll();
		}

		for($i=0,$n=count($res);$i<$n;$i++){
			$this->columns[$res[$i]->Field]["type"] = $res[$i]->Type;
			$this->columns[$res[$i]->Field]["null"] = ($res[$i]->Null === "YES");
			$this->columns[$res[$i]->Field]["default"] = $res[$i]->Default;
			$this->columns[$res[$i]->Field]["extra"] = $res[$i]->Extra;
			$this->columns[$res[$i]->Field]["ai"] = strrpos($res[$i]->Extra,"auto_increment") !== false;

			if($res[$i]->Key === "PRI") $this->keys[] = $res[$i]->Field;
		}
	}

	public function check(DBObject $obj){
		$fields_local = array_keys(get_object_vars($obj));
		$fields_db = array_keys($this->columns);

		sort($fields_local);
		sort($fields_db);

		$comb = array_merge($fields_local,$fields_db);
		sort($comb);
		$comb = array_unique($comb);

		$return = array();
		foreach($comb as $key){
			$return[$key] = array($this->name =>!(array_search($key,$fields_db) === false), get_class($obj) => !(array_search($key,$fields_local) === false));
		}

		return $return;
	}
	
	/**
	 * Hides $this->db-> interface
	 *
	 * @see PDO::prepare
	 *
	 * @author daantimmer
	 *
	 * @param statment
	 * @param options[optional]
	 * @return DBStatement
	 */
	protected function prepare($query, $options = array()){
		return $this->db->prepare($query, $options);
	}

	/**
	 * Basic insert function.
	 *
	 * Extracts information from $obj, builds a query and executes (insert) the query.
	 * $ignore specifies if the query should be an INSERT IGNORE INTO (true)
	 * or INSERT INTO (false) query
	 *
	 * @param mixed $obj the object to insert
	 * @param boolean $ignore
	 * @return boolean
	 */
	public function insert(DBObject $obj, $ignore = false){
		$fields = get_object_vars($obj);

		$collist = array();
		$parlist = array();

		foreach($this->columns AS $var => $set){
			if(isset($fields[$var])){
				$collist[] = "{$var}";
				$parlist[] = ":{$var}";
			}elseif($set["ai"]){
			}elseif((!$set["null"]) && $set["default"] === null){
				throw new Exception("{$var} is null and column has no default value while null is not allowed");
			}
		}

		$ignore = (is_bool($ignore) && $ignore === true) ? "IGNORE " : "";
		$query	=	"INSERT {$ignore} INTO {$this->name}(".implode(",",$collist).") VALUES(".implode(",",$parlist).")";

		$stmt = $this->db->prepare($query);

		foreach($collist AS $var){
			if(isset($obj->{$var}) || !$ignoreNull){
				if( false === strstr($this->columns[$var]["type"],"int") ){
								//not an integer
					if( is_array($obj->{$var}) ){
						$stmt->bindValue(":{$var}", serialize($obj->{$var}));
					}else{
						$stmt->bindParam(":{$var}", $obj->{$var});
					}
					//$stmt->bindParam(":{$var}", $obj->{$var});
				}else{
					if( "" === $obj->{$var} ){
						$stmt->bindValue(":{$var}", null);
					}else{
						$stmt->bindParam(":{$var}", $obj->{$var});
					}
				}
			}
		}

		return $stmt->execute();
	}

	/**
	 * Basic update function.
	 *
	 * $prev is optional, if $prev is not given, the key('s) of $obj are used in the where clause.
	 * This is only useful for columns with only one column (key) or for updates where
	 * one wants to update the key value instead of other values.
	 *
	 * @param DBObject $obj new values
	 * @param DBObject $prev [optional] keys/old values
	 * @return boolean
	 */
	public function update(DBObject $obj, DBObject $prev = null){
		if($prev === null)
		$prev = $obj;

		$where = array();
		foreach($this->keys AS $key) $where[] = "{$key} = :k_{$key}";

		$fields = get_object_vars($obj);

		$collist = array();

		foreach($this->columns AS $var => $set){
			if(isset($fields[$var]) && !$set["ai"]){
				$collist[$var] = "{$var} = :{$var}";
			}
			//			elseif($set["ai"]){
			//			}elseif(!$set["null"] && $set["default"] === null){
			//				throw new Exception("{$var} is null and column has no default value while null is not allowed");
			//			}
		}

		$query	=	"UPDATE {$this->name} SET ".implode(",",$collist)." WHERE ".implode(" AND ",$where);

		$stmt = $this->db->prepare($query);

		//bind values
		foreach($collist AS $key => $n){
			if( false === strstr($this->columns[$key]["type"],"int") ){
				//not an integer
				if( is_array($obj->{$key}) ){
					$stmt->bindValue(":{$key}", serialize($obj->{$key}));
				}else{
					$stmt->bindParam(":{$key}", $obj->{$key});
				}
			}else{
				if( "" === $obj->{$key} ){
					$stmt->bindValue(":{$key}", null);
				}else{
					$stmt->bindParam(":{$key}", $obj->{$key});
				}
			}
		}

		//bind keys
		foreach($this->keys AS $key){
			$stmt->bindParam(":k_{$key}", $prev->{$key});
		}

		return $stmt->execute();
	}

	/**
	 * Basic delete function, alias for DBTable::delete()
	 *
	 * @see DBTable::delete()
	 * @param DBObject $obj
	 * @return boolean
	 */
	public function remove(DBObject $obj){
		return $this->delete($obj);
	}

	/**
	 * Basic delete function.
	 *
	 * Uses the primary key to delete a row
	 *
	 * @param DBObject $obj
	 * @return boolean
	 */
	public function delete(DBObject $obj){
		try {

			$where = array();
			foreach($this->keys AS $key) $where[] = "{$key} = :{$key}";

			$query	=	"DELETE FROM {$this->name} WHERE ".implode(" AND ", $where);
			
			$stmt = $this->db->prepare($query);

			foreach($this->keys AS $key) $stmt->bindParam(":{$key}", $obj->{$key});

			return $stmt->execute();

		}catch(PDOException $e){
			throw new Exception("exception.pdo",$e->errorInfo[1]+0);
			return false;
		}
		return false;
	}
	
	/**
	 * @return String
	 */
	public function getTableName(){
		return $this->name;
	}
	
	/**
	 * Gets the last PDOException
	 *
	 * @return PDOException
	 */
	public function getLastPDOException(){
		return $this->lastError;
	}
}